﻿namespace ScrumFactory.Backlog.Tests
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Moq;
    using ScrumFactory.Backlog;
    using ScrumFactory.Composition.ViewModel;
    using ScrumFactory.Composition;
    using ScrumFactory.Services;

    [TestClass()]
    public class BacklogViewModelTest {
              
        /// <summary>
        /// Checks if the save item to the databse is called when a new item is added.
        /// </summary>
        [TestMethod()]
        public void SavesBacklogItem_OnAddBacklogItemCommand() {

            // (1) CREATE MOCKS            
            var backlogService      = SetupService();                        
            var backgroundExecutor  = SetupExecutor();            
            var aggregator          = SetupAggregator();

            // (2) CREATE TARGET
            var target = new ViewModel.BacklogViewModel(backlogService.Object, backgroundExecutor.Object, aggregator.Object);

            // (3) ASSERT TESTS            
            Assert.IsNotNull(target.Project.BacklogItems, "Items property is null");
            aggregator.Verify(a => a.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, It.IsAny<Action<Project>>()), "<Project> \"ViewProjectDetails\" event was not subscribed");
            backlogService.Verify(s => s.GetCurrentBacklog("project1"), "GetBacklog was not called.");
            
            // adds a new item
            target.NewItemName = "New item title";
            target.CreateNewBacklogItemCommand.Execute(null);
            BacklogItem item = target.Project.BacklogItems.Single<BacklogItem>(i => i.Name == "New item title");

            // checks if the save method was called 
            backlogService.Verify(s => s.UpdateBacklogItem(It.IsAny<BacklogItem>()), "SaveBacklogItem was not called.");
            
        }

        /// <summary>
        /// Checks if a new item is added when the Add Command is executed.
        /// </summary>
        [TestMethod()]
        public void AddsBacklogItem_OnAddBacklogItemCommand() {

            // (1) CREATE MOCKS            
            var backlogService      = SetupService();
            var backgroundExecutor  = SetupExecutor();
            var aggregator          = SetupAggregator();            
            
            // (2) CREATE TARGET
            var target = new ViewModel.BacklogViewModel(backlogService.Object, backgroundExecutor.Object, aggregator.Object);

            // (3) ASSERT TESTS            
            Assert.IsNotNull(target.Project.BacklogItems, "Items property is null");
            aggregator.Verify(a => a.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, It.IsAny<Action<Project>>()), "<Project> \"ViewProjectDetails\" event was not subscribed");
            backlogService.Verify(s => s.GetCurrentBacklog("project1"), "GetBacklog was not called.");

            // checks the existing backlog
            Assert.AreEqual(2, target.Project.BacklogItems.Count, "Items count mismatch");
            Assert.IsTrue(target.Project.BacklogItems.Contains(backlogItem1), "Items does not contains backlog item 1");
            Assert.IsTrue(target.Project.BacklogItems.Contains(backlogItem2), "Items does not contains backlog item 2");

            // adds a new item
            target.NewItemName = "New item title";
            target.CreateNewBacklogItemCommand.Execute(null);

            // check its
            BacklogItem newItem = target.Project.BacklogItems.Single(i => i.Name == "New item title");
            Assert.IsNotNull(newItem, "New item was not inserted");
            Assert.AreEqual(3, target.Project.BacklogItems.Count, "Items count mismatch");
            Assert.IsTrue(target.Project.BacklogItems.Any(i => i.BacklogItemNumber == 3), "New item number mismatch");            
            Assert.IsNotNull(newItem.BacklogItemUId, "New item UId is null");
            Assert.IsNotNull(newItem.CreateDate, "New item CreateDate is null");
            
            // adds another one
            target.NewItemName = "New item title 2";
            target.CreateNewBacklogItemCommand.Execute(null);

            // checks it
            BacklogItem newItem2 = target.Project.BacklogItems.Single(i => i.Name == "New item title 2");
            Assert.IsNotNull(newItem2, "New item was not inserted");
            Assert.IsNotNull(newItem2.BacklogItemUId, "New item UId is null");
            Assert.AreNotEqual(newItem2.BacklogItemUId, newItem.BacklogItemUId, "New Item UId is not unique");

        }

        /// <summary>
        /// Checks if the item is removed when the Delete Command is executed.
        /// </summary>
        [TestMethod()]
        public void RemoveBacklogItem_OnDeleteBacklogItemCommand() {

            // (1) CREATE MOCKS            
            var backlogService = SetupService();
            var backgroundExecutor = SetupExecutor();
            var aggregator = SetupAggregator();

            // (2) CREATE TARGET
            var target = new ViewModel.BacklogViewModel(backlogService.Object, backgroundExecutor.Object, aggregator.Object);

            // (3) ASSERT TESTS            
            Assert.IsNotNull(target.Project.BacklogItems, "Items property is null");
            aggregator.Verify(a => a.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, It.IsAny<Action<Project>>()), "<Project> \"ViewProjectDetails\" event was not subscribed");
            backlogService.Verify(s => s.GetCurrentBacklog("project1"), "GetBacklog was not called.");

            // checks the existing backlog
            Assert.AreEqual(2, target.Project.BacklogItems.Count, "Items count mismatch");            
            
            // deletes a item and checks
            //target.DeleteBacklogItemCommand.Execute(backlogItem2);
            Assert.IsFalse(target.Project.BacklogItems.Contains(backlogItem2), "Items still contains backlog item 2");
        }

        /// <summary>
        /// Checks if the method to delete the item from the databse is called when an item is removed.
        /// </summary>
        [TestMethod()]
        public void DeletesBacklogItem_OnDeleteBacklogItemCommand() {

            // (1) CREATE MOCKS            
            var backlogService = SetupService();
            var backgroundExecutor = SetupExecutor();
            var aggregator = SetupAggregator();

            // (2) CREATE TARGET
            var target = new ViewModel.BacklogViewModel(backlogService.Object, backgroundExecutor.Object, aggregator.Object);

            // (3) ASSERT TESTS            
            Assert.IsNotNull(target.Project.BacklogItems, "Items property is null");
            aggregator.Verify(a => a.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, It.IsAny<Action<Project>>()), "<Project> \"ViewProjectDetails\" event was not subscribed");
            backlogService.Verify(s => s.GetCurrentBacklog("project1"), "GetBacklog was not called.");

            // checks the existing backlog
            Assert.AreEqual(2, target.Project.BacklogItems.Count, "Items count mismatch");

            // deletes the item and checks if a call was made to delete it from the database
            //target.DeleteBacklogItemCommand.Execute(backlogItem2);
            backlogService.Verify(s => s.DeleteBacklogItem(backlogItem2.BacklogItemUId), "DeleteBacklogItem was not called.");

        }

        /// <summary>
        /// Checks if the backlog items are queried on the view project detail event.
        /// </summary>
        [TestMethod()]
        public void QueriesBacklogItems_OnViewProjectDetails()
        {
            // (1) CREATE MOCKS            
            var backlogService = SetupService();
            var backgroundExecutor = SetupExecutor();
            var aggregator = SetupAggregator();

            // (2) CREATE TARGET
            var target = new ViewModel.BacklogViewModel(backlogService.Object, backgroundExecutor.Object, aggregator.Object);

            // (3) ASSERT TESTS            
            Assert.IsNotNull(target.Project.BacklogItems, "Items property is null");
            Assert.AreEqual(2, target.Project.BacklogItems.Count, "Items count mismatch");
            Assert.IsTrue(target.Project.BacklogItems.Contains(backlogItem1), "Items does not contains backlog item 1");
            Assert.IsTrue(target.Project.BacklogItems.Contains(backlogItem2), "Items does not contains backlog item 2");

        }       

        #region Mock Setups

        // Mock objects
        Project project1 = new Project { ProjectUId = "project1" };
        BacklogItem backlogItem1 = new BacklogItem() { BacklogItemUId = "item1", BacklogItemNumber = 1 };
        BacklogItem backlogItem2 = new BacklogItem() { BacklogItemUId = "item2", BacklogItemNumber = 2 };        
        
        Mock<IBackgroundExecutor> SetupExecutor() {            
            var executor = new Mock<IBackgroundExecutor>();

            
            executor.Setup(e => e.StartBackgroundTask<ICollection<BacklogItem>>(
                It.IsAny<Func<ICollection<BacklogItem>>>(),
                It.IsAny<Action<ICollection<BacklogItem>>>()))
                .Callback<Func<ICollection<BacklogItem>>, Action<ICollection<BacklogItem>>>((a, c) => {
                    var r = a();
                    c(r);
                });

            executor.Setup(e => e.StartBackgroundTask(
                It.IsAny<Action>(),
                It.IsAny<Action>()))
                .Callback<Action, Action>((a, c) => {
                    a();
                    c();
                });
            return executor;
        }

        
        Mock<IBacklogService> SetupService() {
            var backlogService = new Mock<IBacklogService>();
            List<BacklogItem> items = new List<BacklogItem> { backlogItem1, backlogItem2 };
            backlogService.Setup(s => s.GetCurrentBacklog("project1")).Returns(items);            
            return backlogService;
        }

        
        Mock<IEventAggregator> SetupAggregator() {
            var aggregator = new Mock<IEventAggregator>();
            aggregator.Setup(a => a.Subscribe<Project>(ScrumFactoryEvent.ViewProjectDetails, It.IsAny<Action<Project>>())).Callback<ScrumFactoryEvent, Action<Project>>((e, del) => {
                del(project1);
            });
            return aggregator;
        }

        #endregion

    }
}
